package com.hitqz.robot.driver.netty.handler;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONException;
import com.alibaba.fastjson.JSONObject;
import com.google.common.cache.Cache;
import com.google.common.util.concurrent.RateLimiter;
import com.hitqz.robot.api.business.enums.RobotOtherCommandEnum;
import com.hitqz.robot.api.common.dto.robot.RobotOnlineEventDto;
import com.hitqz.robot.api.common.dto.robot.RobotPosInfoDto;
import com.hitqz.robot.api.common.dto.tickets.CtrlTicketsEvent;
import com.hitqz.robot.api.common.dto.warning.AddWarningDto;
import com.hitqz.robot.api.common.util.CacheNameUtil;
import com.hitqz.robot.driver.XgRobotDriverImpl;
import com.hitqz.robot.driver.dto.map.MapDataDto;
import com.hitqz.robot.driver.dto.robot.*;
import com.hitqz.robot.driver.service.NettyChannelService;
import com.hitqz.robot.driver.util.*;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import lombok.extern.slf4j.Slf4j;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.locks.Lock;

/**
 * @author xupkun
 * @date 2023/8/4
 */
@Slf4j
@ChannelHandler.Sharable
public class RobotControllerMessageHandler extends ChannelInboundHandlerAdapter {

    private SourceEnum sourceEnum;

    public RobotControllerMessageHandler(SourceEnum source) {
        this.sourceEnum = source;
    }

    private final static RateLimiter publishLowPowerLimiter = RateLimiter.create(1);

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        NettyChannelService.addChannel(sourceEnum.getSource(), ctx);
        if(sourceEnum ==SourceEnum.ROBOT_STATUS) {
            CompletableFuture.runAsync(()->  {
                NettyChannelService.changeRobotOnline(0);
            });
        }
        super.channelActive(ctx);
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        NettyChannelService.removeChannel( sourceEnum, ctx);
        // 重新连接
        log.info("连接断开:" + sourceEnum.getSource());
        super.channelInactive(ctx);
        if (sourceEnum == SourceEnum.ROBOT_STATUS) {
            CompletableFuture.runAsync(()-> {
                RobotOnlineEventDto robotChangeOnlineStatusDto = new RobotOnlineEventDto();
                robotChangeOnlineStatusDto.setOnlineStatus(0);
                SpringUtil.getApplicationContext().publishEvent(robotChangeOnlineStatusDto);
            });
        }
    }

    private boolean hasLog= false;
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        CompletableFuture.runAsync(()->{
            MessageRobotDto dto = (MessageRobotDto) msg;
            String data = dto.getData().trim();
            Lock lock = null;
            InetSocketAddress address = (InetSocketAddress) ctx.channel().remoteAddress();
            String ip = address.getAddress().getHostAddress();
            switch(dto.getCmd()) {
                case 19301:
                    // 主动推送内容
                    Cache<String, RobotPushDto> cache = SpringUtil.getBean(CacheNameUtil.CACHE_ROBOT_INFO);
                    RobotPushDto pushDto  = JSONObject.parseObject(data, RobotPushDto.class);
                    RobotPushDto lastPushDto = Optional.ofNullable((RobotPushDto) cache.getIfPresent(ip)).orElse(new RobotPushDto());
                    this.compareStatus(pushDto, lastPushDto);
                    cache.put(ip, pushDto);
                    Cache<String, RobotPosInfoDto> posInfoDtoCache = SpringUtil.getBean(CacheNameUtil.CACHE_POS_INFO);
                    RobotPosInfoDto infoDto  = JSONObject.parseObject(data, RobotPosInfoDto.class);
                    posInfoDtoCache.put(ip, infoDto);
                    synchronized (this) {
                        if (pushDto.getTaskStatus() != 4) {
                            hasLog = false;
                        }

                        if ((CollectionUtil.isNotEmpty(pushDto.getPgvs()) && pushDto.getTaskStatus() == 4)
                                && (!hasLog)) {
                            log.info("{} ==== {} --- {}  pgvs: x{}--y {}, angle: {}" ,
                                    hasLog, pushDto.getTaskStatus(),
                                    pushDto.getTaskStatusPackage().getTargetName(),
                                    new BigDecimal(pushDto.getPgvs().get(0).getTagDiffX()).setScale(10,RoundingMode.HALF_UP),
                                    new BigDecimal(pushDto.getPgvs().get(0).getTagDiffY()).setScale(10, RoundingMode.HALF_UP),
                                    new BigDecimal(pushDto.getPgvs().get(0).getTagDiffAngle()).setScale(10,RoundingMode.HALF_UP)
                            );
                            hasLog = true;
                        }
                    }
                    if (!Objects.equals(lastPushDto.getWarnings(),pushDto.getWarnings())) {
                        if (StrUtil.isNotBlank(pushDto.getFatals()) && !JSONArray.parseArray(pushDto.getWarnings()).isEmpty()) {
                            parseWarning(pushDto.getFatals() , 1);
                        }
                    }
                    if (!Objects.equals(pushDto.getFatals(),lastPushDto.getFatals())) {
                        parseWarning(pushDto.getFatals() , 1);
                    }
                    if (!Objects.equals(pushDto.getErrors(),lastPushDto.getErrors())) {
                        parseWarning(pushDto.getErrors(), 1);
                    }
                    if (!Objects.equals(pushDto.getNotices(),lastPushDto.getNotices())) {
                        parseWarning(pushDto.getNotices() ,1);
                        //
                    }
                    break;
                case 11021:
                    lock = RobotStatusUtil.ROBOT_STATUS_RELOC_LOCK;
                    JSONObject jsonObject = JSONObject.parseObject(data);
                    if (jsonObject.containsKey("reloc_status")) {
                        RobotStatusUtil.ROBOT_RELOC_STATUS = jsonObject.getInteger("reloc_status");
                    }
                    break;
                case 11027:
                    lock = RobotStatusUtil.ROBOT_QUERY_JACK_STATUS_LOCK;
                    jsonObject = JSONObject.parseObject(data);
                    Integer jackStatus = jsonObject.getInteger("jack_state");
                    RobotStatusUtil.LIFT_STATUS = jackStatus;
                    break;
                case 11301:
                    RobotQueryStationsRetDto queryStationsRetDto  = JSONObject.parseObject(data, RobotQueryStationsRetDto.class);
                    lock = RobotStatusUtil.ROBOT_QUERY_MAP_POS;
                    RobotStatusUtil.ROBOT_STATIONS = queryStationsRetDto;
                    break;
                case 11303:
                    RobotQueryPathDto robotQueryPathDto  = JSONObject.parseObject(data, RobotQueryPathDto.class);
                    log.info("path" + JSONObject.toJSONString(robotQueryPathDto));
                    lock = RobotStatusUtil.ROBOT_QUERY_PATH;
                    RobotStatusUtil.ROBOT_QUERY_PATH_DATA = robotQueryPathDto;
                    break;
                case 11300:
                    RobotQueryMapDto robotQueryMapDto = JSONObject.parseObject(data, RobotQueryMapDto.class);
                    lock = RobotStatusUtil.ROBOT_QUERY_MAP;
                    log.info("ROBOT_QUERY_MAP" + data);
                    RobotStatusUtil.ROBOT_QUERY_MAP_DATA = robotQueryMapDto;
                    break;
                case 14011:
                    // 从机器人下载地图, 实际上只能获取到地图上面的数据
                    lock = RobotStatusUtil.ROBOT_QUERY_MAP_ATTRIBUTES;
                    RobotStatusUtil.ROBOT_QUERY_ATTRIBUTES_DATA = JSONObject.parseObject(data, MapDataDto.class);
                    break;
                case 13051:
                    log.info("发送前往目标点结果:" + data);
                    lock = RobotStatusUtil.ROBOT_GO_TARGET_POS;
                    jsonObject = JSONObject.parseObject(data);
                    RobotStatusUtil.ROBOT_GO_TARGET_RESULT = jsonObject.getInteger("ret_code") == 0;
                    break;
                case 11071:
                    jsonObject = JSONObject.parseObject(data);
                    RobotStatusUtil.ROBOT_MAX_SPEED_RESULT = jsonObject;
                    break;
                default:
                    //log.info("other command result: {} and result {} ", dto.getCmd(), data);
            }
            if (lock != null) {
                synchronized (lock) {
                    try {
                        lock.notifyAll();
                    } catch (Exception e) {
                        log.error("释放锁失败:{}", e.getMessage());
                    }
                }
            }
        });
        super.channelRead(ctx, msg);
    }
  // 43.32
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        NettyChannelService.removeChannel(sourceEnum,  ctx);
        // 重新连接
        InetSocketAddress remoteAddress = (InetSocketAddress) ctx.channel().remoteAddress();
        String ip = remoteAddress.getAddress().getHostAddress();
        log.info("连接断开" + sourceEnum.getSource() + "ip: " + ip);
        super.userEventTriggered(ctx, evt);
        NettyChannelService.connect(ip, sourceEnum);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        if (!ctx.channel().isActive()) {
            NettyChannelService.removeChannel(sourceEnum, ctx);
            // 重新连接
            log.info("连接断开:cat" + sourceEnum.getSource());
            super.exceptionCaught(ctx, cause);
        }

    }

    private void parseWarning(String warningObj, Integer level) {
        if (StrUtil.isBlank(warningObj)) {
            return;
        }
        JSONArray jsonArray = JSONArray.parseArray(warningObj);
        if (!jsonArray.isEmpty()) {
            log.info("错误信息的数据:" + warningObj);
            int size = jsonArray.size();
            for (int i = 0; i < size; i++) {
                JSONObject jsonObject = jsonArray.getJSONObject(i);
                String code = extractCode(jsonObject);
                String content = extractContent(jsonObject);
                String suggestion = "请联系管理员，或者运维人员排查。";
                AddWarningDto addWarningDto = new AddWarningDto();
                addWarningDto.setCode(code);
                addWarningDto.setTitle("控制器报警");
                log.info("报警码是:" + code);
                RobotErrorEnum errorEnum = RobotErrorEnum.ERRORS.get(code);
                if (errorEnum != null) {
                    content = errorEnum.getMessage();
                    suggestion = errorEnum.getSuggestion();
                    addWarningDto.setContent(content);
                    addWarningDto.setSuggestion(suggestion);
                    addWarningDto.setLevel(level);
                    SpringUtil.getApplicationContext().publishEvent(addWarningDto);
                    // 清除报警warning
                    customSendMessage(SourceEnum.ROBOT_CONFIG.getSource(), 0x0FA9, null);
                    if (errorEnum.isStopTask()){
                        // 暂停
                        log.info("[xg] 告警需要暂停任务");
                        CtrlTicketsEvent ctrlTicketsEvent = new CtrlTicketsEvent();
                        ctrlTicketsEvent.setType(CtrlTicketsEvent.TYPE_PAUSE);
                        ctrlTicketsEvent.setMessage(errorEnum.getMessage() + ",任务暂停");
                        ctrlTicketsEvent.setTicketsId(null);
                        SpringUtil.getApplicationContext().publishEvent(ctrlTicketsEvent);
                    }
                } else {
                    log.info("出现其他不重要的故障: {}---{}", code, content);
                }
            }
        }
    }

    private String extractCode(JSONObject jsonObject) {
        if (jsonObject.containsKey("code")) {
            return String.valueOf(jsonObject.getInteger("code"));
        }
        for (String key : jsonObject.keySet()) {
            if (isErrorCodeKey(key)) {
                return extractValue(jsonObject, key);
            }
        }
        return null;
    }

    private boolean isErrorCodeKey(String key) {
        return key.equals("code") ||
                (!Objects.equals(key, "dateTime") &&
                        !Objects.equals(key, "times") &&
                        !Objects.equals(key, "desc") && !Objects.equals(key, "reason"));
    }

    private String extractValue(JSONObject jsonObject, String key) {
        try {
            return jsonObject.getString(key);
        } catch (JSONException e) {
            log.warn("解析错误码时出现异常: key=" + key + ", value=" + jsonObject.get(key), e);
            return null;  // 或者抛出异常取决于业务需求
        }
    }

    private String extractContent(JSONObject jsonObject) {
        Object descValue = jsonObject.get("desc");
        if (descValue instanceof String) {
            String content = (String) descValue;
            if (content.contains("]")) {
                String[] parts = content.split("]");
                int index = Math.min(0, parts.length);
                return index == 0 ? content : parts[1];
            }
            return content;
        }
        return "";
    }

    public void customSendMessage(String sourceEnum, Integer cmd, Map params) {
        byte[] data = null;
        if (ObjectUtil.isNotEmpty(params)) {
            data = new byte[10];
            data = JSONObject.toJSONString(params).getBytes(StandardCharsets.UTF_8);
        }
        NettyChannelService.sendMessage(sourceEnum,
                SeqUtil.nextId(), cmd, data);
    }


    private void compareStatus(RobotPushDto current,RobotPushDto last){
        try{
            if (current.isBlocked() && !last.isBlocked()){
                //避障触发
                publishWarningEvent("避障告警", "激光雷达触发，且30秒内位置未发生变化", "", "查看机器人周围是否有障碍物", 2);
            }
            double chargeThreshold = XgConstant.robot.getChargeThreshold() * 0.01d;
            if (!current.isCharging() && current.getBatteryLevel() < chargeThreshold  && publishLowPowerLimiter.tryAcquire(60*5)) {
                if (XgConstant.isAutoCharge){
                    log.info("已经在前往自主充电的路上");
                }else {
                    log.info("触发自主充电");
                    XgConstant.isAutoCharge = true;
                    publishWarningEvent("自主充电告警", "电量低于充电阈值" + XgConstant.robot.getChargeThreshold() + "%,自主关闭任务并返回充电位置充电", "", "", 1);
                    new Thread(() -> {
                        //暂停任务
                        CtrlTicketsEvent ctrlTicketsEvent = new CtrlTicketsEvent();
                        ctrlTicketsEvent.setType(CtrlTicketsEvent.TYPE_PAUSE);
                        ctrlTicketsEvent.setMessage("触发自主充电,任务暂停");
                        ctrlTicketsEvent.setTicketsId(null);
                        SpringUtil.getApplicationContext().publishEvent(ctrlTicketsEvent);
                        try {
                            //所以里面加个小延迟没关系，避免暂停任务和返回充电的时序问题
                            Thread.sleep(2000);
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                        XgRobotDriverImpl robotDriver = SpringUtil.getBean(XgRobotDriverImpl.class);
                        robotDriver.executeOtherCommand(RobotOtherCommandEnum.GO_HOME, "");
                    }).start();
                }
            }
            if (current.isCharging() && XgConstant.isAutoCharge){
                XgConstant.isAutoCharge = false;
            }
        } catch (Exception e) {

        }
    }


    public static void publishWarningEvent(String title,String content,String code,String suggestion,Integer level) {
        AddWarningDto addWarningDto = new AddWarningDto();
        addWarningDto.setTitle(title);
        addWarningDto.setCode(code);
        addWarningDto.setContent(content);
        addWarningDto.setSuggestion(suggestion);
        addWarningDto.setLevel(level);
        SpringUtil.getApplicationContext().publishEvent(addWarningDto);
    }


}
